package phase2.rd;

import phase2.rd.Token.Type;

/**
 * TODO Check that there are enough tokens
 * 
 * @author John Girata
 * @version Dec 5, 2012
 */
public class RecursiveDescender {
	private TokenList tokens;

	public RecursiveDescender(TokenList tokens) {
		this.tokens = tokens;
	}

	public Node process() throws ParseException {
		Node result = miniREProgram(new TokenList(tokens));
		
		if (result == null) {
			throw new ParseException();
		}
		
		return result;
	}

	static Node miniREProgram(TokenList tokens) {
		if (tokens.isEmpty() || !tokens.remove(0).equals(Type.KEYWORD, "begin")) {
			return null;
		}
		
		InteriorNode statements = new InteriorNode("statements");
		
		if (!statementList(tokens, statements)) {
			return null;
		}
		
		if (tokens.isEmpty() || !tokens.remove(0).equals(Type.KEYWORD, "end")) {
			return null;
		}
		
		return statements;
	}

	static boolean statementList(TokenList tokens, InteriorNode statements) {
		if (tokens.isEmpty()) {
			return false;
		}
		
		Node statement = statement(tokens);
		
		if (statement == null) {
			return false;
		}
		
		statements.addChild(statement);
		statementListTail(tokens, statements);
		
		return true;
	}

	static void statementListTail(TokenList tokens, InteriorNode statements) {
		if (tokens.isEmpty()) {
			return;
		}
		
		Node statement = statement(tokens);
		
		if (statement == null) {
			return;
		}
		
		statements.addChild(statement);
		statementListTail(tokens, statements);
	}

	static Node statement(TokenList tokens) {
		Node result = statement1(tokens);
		
		if (result != null) {
			return result;
		}

		result = statement2(tokens);
		
		if (result != null) {
			return result;
		}

		result = statement3(tokens);
		
		if (result != null) {
			return result;
		}

		result = statement4(tokens);
		
		if (result != null) {
			return result;
		}

		result = statement5(tokens);
		
		if (result != null) {
			return result;
		}

		result = statement6(tokens);
		
		if (result != null) {
			return result;
		}
		
		return null;
	}

	static Node statement1(TokenList tokens) {
		TokenList copy = new TokenList(tokens);

		Token result = copy.remove(0);

		if (result.getType() != Type.ID) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "=")) {
			return null;
		}

		Node exp = exp(copy);

		if (exp == null) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ";")) {
			return null;
		}

		tokens.clear();
		tokens.addAll(copy);

		return new InteriorNode("assign", new Node(result.getValue()), exp);
	}

	static Node statement2(TokenList tokens) {
		TokenList copy = new TokenList(tokens);

		Token result = copy.remove(0);

		if (result.getType() != Type.ID) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "=")) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "#")) {
			return null;
		}

		Node exp = exp(copy);

		if (exp == null) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ";")) {
			return null;
		}

		tokens.clear();
		tokens.addAll(copy);

		return new InteriorNode("length", new Node(result.getValue()), exp);
	}

	static Node statement3(TokenList tokens) {
		// ID = maxfreqstring (ID);
		TokenList copy = new TokenList(tokens);

		Token result = copy.remove(0);

		if (result.getType() != Type.ID) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "=")) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "maxfreqstring")) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "(")) {
			return null;
		}

		Token target = copy.remove(0);

		if (target.getType() != Type.ID) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ")")) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ";")) {
			return null;
		}

		tokens.clear();
		tokens.addAll(copy);

		return new InteriorNode("maxfreqstring", new Node(result.getValue()),
				new Node(target.getValue()));
	}

	static Node statement4(TokenList tokens) {
		TokenList copy = new TokenList(tokens);

		if (!copy.remove(0).equals(Type.KEYWORD, "replace")) {
			return null;
		}

		Token regex = copy.remove(0);

		if (regex.getType() != Type.REGEX) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "with")) {
			return null;
		}

		Token ascii = copy.remove(0);

		if (ascii.getType() != Type.ASCII) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "in")) {
			return null;
		}

		Node fileNames = fileNames(copy);

		if (fileNames == null) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ";")) {
			return null;
		}

		tokens.clear();
		tokens.addAll(copy);

		return new InteriorNode("replace", new Node(regex.getValue()),
				new Node(ascii.getValue()), fileNames);
	}

	static Node statement5(TokenList tokens) {
		TokenList copy = new TokenList(tokens);

		if (!copy.remove(0).equals(Type.KEYWORD, "recursivereplace")) {
			return null;
		}

		Token regex = copy.remove(0);

		if (regex.getType() != Type.REGEX) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "with")) {
			return null;
		}

		Token ascii = copy.remove(0);

		if (ascii.getType() != Type.ASCII) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "in")) {
			return null;
		}

		Node fileNames = fileNames(copy);

		if (fileNames == null) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ";")) {
			return null;
		}

		tokens.clear();
		tokens.addAll(copy);

		return new InteriorNode("recursivereplace", new Node(regex.getValue()),
				new Node(ascii.getValue()), fileNames);
	}

	static Node statement6(TokenList tokens) {
		TokenList copy = new TokenList(tokens);

		if (!copy.remove(0).equals(Type.KEYWORD, "print")) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "(")) {
			return null;
		}

		InteriorNode in = new InteriorNode("print");
		expList(copy, in);

		if (!copy.remove(0).equals(Type.KEYWORD, ")")) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ";")) {
			return null;
		}

		tokens.clear();
		tokens.addAll(copy);

		return in;
	}

	static Node fileNames(TokenList tokens) {
		TokenList copy = new TokenList(tokens);

		Node source = sourceFile(copy);

		if (source == null) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ">!")) {
			return null;
		}

		Node destination = destinationFile(copy);

		if (destination == null) {
			return null;
		}

		tokens.clear();
		tokens.addAll(copy);

		return new InteriorNode("filenames", source, destination);
	}

	static Node sourceFile(TokenList tokens) {
		Token next = tokens.get(0);

		if (next.getType() == Type.ASCII) {
			tokens.remove(0);
			return new Node(next.getValue());
		}

		return null;
	}

	static Node destinationFile(TokenList tokens) {
		Token next = tokens.get(0);

		if (next.getType() == Type.ASCII) {
			tokens.remove(0);
			return new Node(next.getValue());
		}

		return null;
	}

	static void expList(TokenList tokens, InteriorNode node) {
		Node exp = exp(tokens);

		if (exp == null) {
			return;
		}

		node.addChild(exp);
		expListTail(tokens, node);
	}

	static void expListTail(TokenList tokens, InteriorNode node) {
		TokenList copy = new TokenList(tokens);

		if (!copy.remove(0).equals(Type.KEYWORD, ",")) {
			return;
		}

		Node exp = exp(copy);

		if (exp == null) {
			return;
		}

		node.addChild(exp);
		expListTail(copy, node);

		tokens.clear();
		tokens.addAll(copy);
	}

	static Node exp(TokenList tokens) {
		Node node = exp1(tokens);

		if (node != null) {
			return node;
		}

		return exp2(tokens);
	}

	static Node exp1(TokenList tokens) {
		if (tokens.get(0).getType() == Type.ID) {
			return new Node(tokens.remove(0).getValue());
		}

		TokenList copy = new TokenList(tokens);

		if (!copy.remove(0).equals(Type.KEYWORD, "(")) {
			return null;
		}

		Node exp = exp(copy);

		if (exp == null) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, ")")) {
			return null;
		}

		tokens.clear();
		tokens.addAll(copy);

		return exp;
	}

	static Node exp2(TokenList tokens) {
		TokenList copy = new TokenList(tokens);
		Node term = term(copy);

		if (term == null) {
			return null;
		}

		BinaryNode op = expTail(copy);

		if (op == null) {
			tokens.clear();
			tokens.addAll(copy);
			return term;
		}

		BinaryNode temp = op;

		while (temp.getLeft() != null) {
			if (temp.getLeft() instanceof BinaryNode) {
				temp = (BinaryNode) temp.getLeft();
			} else {
				throw new RuntimeException("This shouldn't happen");
			}
		}

		temp.setLeft(term);
		tokens.clear();
		tokens.addAll(copy);

		return op;
	}

	static BinaryNode expTail(TokenList tokens) {
		TokenList copy = new TokenList(tokens);
		BinaryNode op = binOp(copy);

		if (op == null) {
			return null;
		}

		Node term = term(copy);

		if (term == null) {
			return null;
		}

		op.setRight(term);
		BinaryNode next = expTail(copy);

		tokens.clear();
		tokens.addAll(copy);

		if (next != null) {
			next.setLeft(op);
			return next;
		} else {
			return op;
		}
	}

	static Node term(TokenList tokens) {
		TokenList copy = new TokenList(tokens);

		if (!copy.remove(0).equals(Type.KEYWORD, "find")) {
			return null;
		}

		Token regex = copy.remove(0);

		if (regex.getType() != Type.REGEX) {
			return null;
		}

		if (!copy.remove(0).equals(Type.KEYWORD, "in")) {
			return null;
		}

		Node fileName = fileName(copy);

		if (fileName == null) {
			return null;
		}

		InteriorNode term = new InteriorNode("find");
		term.addChild(new Node(regex.getValue()));
		term.addChild(fileName);

		tokens.clear();
		tokens.addAll(copy);

		return term;
	}

	static Node fileName(TokenList tokens) {
		Token next = tokens.get(0);

		if (next.getType() == Type.ASCII) {
			tokens.remove(0);
			return new Node(next.getValue());
		}

		return null;
	}

	static BinaryNode binOp(TokenList tokens) {
		Token next = tokens.get(0);

		if (next.getType() != Type.KEYWORD) {
			return null;
		}

		String value = next.getValue();

		if (value.equals("diff") || value.equals("union")
				|| value.equals("inters")) {
			BinaryNode node = new BinaryNode(next.getValue());
			tokens.remove(0);
			return node;
		}

		return null;
	}
}
